﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection.Emit;
using System.Reflection;
using Hydra.Shell.Interfaces;
using System.Threading;

namespace Hydra.Shell.Implementations
{
    public static class HydraServiceWrapperCreator
    {
        public static IUnknown Wrap(this IUnknown objectToWrap, Guid serviceGuid)
        {
            return Create(objectToWrap.GetType(), serviceGuid, objectToWrap); 
        }

        private static IUnknown Create(Type typeToWrap, params object[] args)
        {
            var domain = Thread.GetDomain();
            var assemblyBuilder = BuildAssemblyHydraShellClient(domain, typeToWrap);
            var moduleBuilder = BuildModuleHydraShellClient_dll(assemblyBuilder);
            var typeBuilder = BuildHydraServiceWrapper(moduleBuilder);
            var constructorBuilder = BuildConstructor(typeBuilder);

            var type = typeBuilder.CreateType();

            return (IUnknown) Activator.CreateInstance(type, args);
        }

        private static AssemblyBuilder BuildAssemblyHydraShellClient(AppDomain domain, Type typeToWrap)
        {
            var assemblyName = new AssemblyName("HydraShellClient.Dynamic");
            assemblyName.Version = new Version(1, 0, 0, 0);

            var assembly = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            return assembly;
        }

        private static ModuleBuilder BuildModuleHydraShellClient_dll(AssemblyBuilder assembly)
        {
            var module = assembly.DefineDynamicModule("HydraShellClient.Dynamic.dll");
            return module;
        }

        private static TypeBuilder BuildHydraServiceWrapper(ModuleBuilder module)
        {
            TypeBuilder type = module.DefineType("Hydra.Shell.Implementations.Dynamic.HydraServiceWrapper", TypeAttributes.Public, typeof(object), new Type[0]);

            return type;
        }

        private static ConstructorBuilder BuildConstructor(TypeBuilder type)
        {
            // Declaring method builder
            // Method attributes

            var methodAttributes = MethodAttributes.Public;
            var constructor = type.DefineConstructor(methodAttributes, CallingConventions.Standard, new Type[] { typeof(Guid), typeof(IUnknown) });

            // Preparing Reflection instances

            var ctorBase = typeof(HydraServiceWrapper).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(Guid), typeof(IUnknown) }, null);

            // Parameter serviceGuid

            var serviceGuid = constructor.DefineParameter(1, ParameterAttributes.None, "serviceGuid");

            // Parameter service

            var service = constructor.DefineParameter(2, ParameterAttributes.None, "service");

            // Generate

            var gen = constructor.GetILGenerator();

            // Writing body

            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Ldarg_2);
            gen.Emit(OpCodes.Call, ctorBase);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Ret);

            // finished

            return constructor;
        }
    }
}
